Javascript 是同步的語言意指指令由上而下, 一步一步的執行程式碼, 是同步一次只能作一件事, 且依序做
基本執行順序, 由上往下執行
consle.log("1");
consle.log("2");
consle.log("3");
/*結果
1
2
3
*/
觀察下段程式碼, getTimem為取得現在時間戳的函數, passTime經過時間函數, block利用while迴圈製作的模擬阻塞函數, 可以觀察到確實block函數阻塞了程式執行, 因為一步一步執行所以同步會有阻塞
const getTime = function () {
return Date.now();
};
const passTime = function () {
return Date.now() - timeStart;
};
const block = function (seconds) {
let time = 1000 * seconds;
console.log(`阻塞${seconds}秒開始`);
while (passTime() < time) {}
console.log(`阻塞${seconds}秒結束`);
};
console.log("步驟1");
let timeStart = getTime();
block(2);
console.log("步驟2");
/*結果
步驟1
阻塞2秒開始
阻塞2秒結束
步驟2
*/
block改用timeout模擬, 發現沒有阻塞作用, 可以發現setTimeout不是同步的
const block = function (seconds) {
let time = 1000 * seconds;
console.log("計時器開始");
setTimeout(function () {
console.log(`計時器${seconds}秒結束`);
}, time);
};
console.log("步驟1");
block(2);
console.log("步驟2");
/*結果
步驟1
計時器開始
步驟2
計時器2秒結束
*/
JavaScript本身是同步的程式語言, 加上環境的API例如瀏覽器提供的setTimeoout與事件觸發才能有非同步產生, 運作方式是將非同步的程式碼放入一個事件佇列(Event queue)中, 等到所有其他同步程式碼執行完後, 在條件觸發下將相關的非同步程式從Event queue取出並執行, 例如setTimeout(funcA, 1000) 就是計時1秒後就執行funcA, 但setTimeout不會對下一行程式碼造成阻塞, 如此一來非同步不會阻塞可以提高JavaScript程式碼的執行效率, 但也因此必須重新考慮程式執行順序, 要正確調整執行順序可以利用JavaScript的一個特性, First Class Function 函數就是一種物件, 所以函數可以當參數使用, 這種機制就是callback, 觀察下面兩塊程式碼, funcA模擬重網路取得資料, setTimeout用來模擬取得資料所費時間, funB要利用取得的資料來作某些事
第1區塊程式看funcA之後執行funcB, 乍看順序沒問題, 但因為非同步關係導致整體執行順序與要求不同
let funcA = function () {
console.log("A開始取得資料");
setTimeout(function () {
console.log("A取得資料結束");
}, 1000);
};
let funcB = function () {
console.log("利用A取的資料做事");
};
funcA();
funcB();
/*結果 順序有問題
A取得資料結束
利用A取的資料做事
A取得資料結束
*/
第2塊程式碼, 將funcB 當作funcA參數傳入, 取得資料結束後就執行funcB, 這樣執行順序符合要求
let funcA = function (callback) {
console.log("A開始取得資料");
setTimeout(function () {
console.log("A取得資料結束");
callback();
}, 1000);
};
let funcB = function () {
console.log("利用A取的資料做事");
};
funcA(funcB);
/*結果 順序正確
A取得資料結束
A取得資料結束
利用A取的資料做事
*/
當有大量的非同步又必須依照順序來執行時, 就可能會出現多層callback, 俗稱的callback hell, 這會造成痛苦的debug, 這問題可以用ES6提供了Promise物件來避免